#include <graphics/sprite2d.h>

#include <graphics/vertexarray.h>
#include <graphics/vertexbuffer.h>
#include <graphics/indexbuffer.h>
#include <graphics/mesh.h>
#include <graphics/material.h>
#include <render/texture2d.h>
#include <graphics/image2d.h>

namespace yam3d
{
namespace graphics
{

namespace
{
    class SpriteFactory
    {
    public:
        void registerSprite()
        {
            ++sprites;
        }

        void unregisterSprite()
        {
            assert( sprites > 0 );
            if( --sprites == 0 )
            {
                m_mesh  = 0;
                m_shaderProgram = 0;
                m_ss = 0;
                m_rs = 0;
            }
        }

        int getSpriteCount()
        {
            return sprites;
        }

        static SpriteFactory* instance()
        {
            if( m_instance == 0)
            {
                m_instance = new SpriteFactory();
            }

            return m_instance;
        }

        static void free()
        {
            assert( m_instance != 0);

            delete m_instance;
            m_instance = 0;
        }

        const graphics::Mesh* const getMesh() const
        {
            return m_mesh.ptr();
        }

        core::Ref<Effect> createNewEffect(render::Texture* texture) const
        {

            core::Ref<Pass> pass = new Pass();
            pass->setTexture(0, "sBaseMap", texture);
       //     pass->setSamplerState(0,createNewSamplerState());
       //     pass->setRenderState(createNewRenderState());
       //     pass->setShaderProgram(createNewShaderProgram());

            pass->setSamplerState(0,m_ss.ptr());
            pass->setRenderState(m_rs.ptr());
            pass->setShaderProgram(m_shaderProgram.ptr());

            pass->setCameraRef("NO_CAMERA");
            pass->setShaderUniforms(new graphics::ShaderUniforms());

            core::Ref<Effect> effect = new Effect();
            effect->setPass( 0, pass );

            return effect.ptr();
        }

    private:
        static core::Ref<RenderState> createNewRenderState()
        {
            core::Ref<RenderState> rs = new RenderState();
            rs->setCullMode( RenderState::CULL_NONE );
            rs->setDepthEnable( false );
            return rs;
        }

        static core::Ref<SamplerState> createNewSamplerState()
        {
            core::Ref<SamplerState> ss = new SamplerState();
            ss->setFiltering(SamplerState::NEAREST, SamplerState::NEAREST);
            ss->setWrapping(SamplerState::CLAMP, SamplerState::CLAMP, SamplerState::CLAMP);
            return ss;
        }

        static core::Ref<Mesh> createNewMesh()
        {
            // VB
            VertexBuffer* vb = new VertexBuffer();

            // Indices for sprite (screen size quad)
            core::vector<unsigned int> indices;
            indices.push_back(0);
            indices.push_back(1);
            indices.push_back(2);
            indices.push_back(2);
            indices.push_back(3);
            indices.push_back(0);
            core::Ref<IndexBuffer> ib = new IndexBuffer(indices);

            {
                // Positions
                core::vector<math::vec3> positions;
                positions.push_back( math::vec3(-1.0,-1.0, 0.0) );
                positions.push_back( math::vec3( 1.0,-1.0, 0.0) );
                positions.push_back( math::vec3( 1.0, 1.0, 0.0) );
                positions.push_back( math::vec3(-1.0, 1.0, 0.0) );
                vb->addVertexArray("a_position", new VertexArray(positions) );

                // Texture coordinates. Texture origin is at top left corner.
                core::vector<math::vec2> texCoords;
                texCoords.push_back( math::vec2(0.0,1.0) );
                texCoords.push_back( math::vec2(1.0,1.0) );
                texCoords.push_back( math::vec2(1.0,0.0) );
                texCoords.push_back( math::vec2(0.0,0.0) );
                vb->addVertexArray("a_texCoord", new VertexArray(texCoords) );
            }

            return new graphics::Mesh(vb,ib);
        }


        static core::Ref<render::ShaderProgram> createNewShaderProgram()
        {
            // Shaders
            core::Ref<render::ShaderProgram> shaderProgram = new render::ShaderProgram();
            assert( shaderProgram );

            char vertexShaderStr[] =
                "uniform highp vec2 vecCropStart;                        \n"
                "uniform highp vec2 vecCropSize;                         \n"
                "uniform highp vec2 vecViewportSize;                     \n"
                "uniform highp vec2 vecInvViewportSize;                  \n"
                "uniform highp mat4 matTransform;                  \n"

                "attribute highp vec4 a_position;                  \n"
                "attribute highp vec2 a_texCoord;                        \n"

                "varying mediump vec2 vTexCoord;                           \n"

                "void main()                                                                                                           \n"
                "{                                                                                                                     \n"
                "   vec4 tempPos = matTransform*a_position;                                                                            \n"
                "   gl_Position = vec4( tempPos.x*vecInvViewportSize.x, tempPos.y*vecInvViewportSize.y, tempPos.z, tempPos.w );        \n"
                "   vTexCoord   = vecCropStart+(a_texCoord*vecCropSize);                                                               \n"
                "}                                                                                                                     \n";

            shaderProgram->addShaderFromSourceCode(QGLShader::Vertex, vertexShaderStr);

            char fragmentShaderStr[] =
                "varying mediump vec2 vTexCoord;                            \n"
                "uniform sampler2D sBaseMap;                        \n"

                "void main()                                                                                          \n"
                "{                                                                                                    \n"
                "   mediump vec4 color = texture2D( sBaseMap, vTexCoord );                                              \n"
                "   gl_FragColor = color;                                                                             \n"
                "}                                                                                                    \n";

            shaderProgram->addShaderFromSourceCode(QGLShader::Fragment, fragmentShaderStr);

            if( !shaderProgram->link() )
            {
                qDebug( shaderProgram->log().toStdString().c_str() );
            }

            assert( shaderProgram->isLinked() );
            shaderProgram->bind();
            return shaderProgram;
        }

        SpriteFactory()
            : sprites(0)
            , m_mesh(createNewMesh())
        {
            m_mesh = createNewMesh();
            m_rs = createNewRenderState();
            m_ss = createNewSamplerState();
            m_shaderProgram = createNewShaderProgram();
        }

        ~SpriteFactory()
        {
        }

        int sprites;
        core::Ref<Mesh>                     m_mesh;
        core::Ref<render::ShaderProgram>    m_shaderProgram;
        core::Ref<SamplerState>             m_ss;
        core::Ref<RenderState>              m_rs;

        static SpriteFactory*               m_instance;
    };

    SpriteFactory* SpriteFactory::m_instance = 0;

}




Sprite2D::Sprite2D(render::Texture2D* texture)
    : core::Object(__FUNCTION__)
    , m_effect(0)
    , m_crop( Rect(math::vec2(0,0),math::vec2(1.0,1.0)))
    , m_translation(0,0)
    , m_scale(1,1)
    , m_rotation(0)
    , m_textureSize( texture->getImage()->width(), texture->getImage()->height() )
{

 //   qDebug( "Sprite2D DAA1" );
    SpriteFactory::instance()->registerSprite();
  //  qDebug( "Sprite2D DAA2" );

    m_effect = SpriteFactory::instance()->createNewEffect( (render::Texture*)texture );

    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vecCropStart", m_crop.getStart());
    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vecCropSize", m_crop.getSize());

    updateTransform();
}


Sprite2D::~Sprite2D()
{
     SpriteFactory::instance()->unregisterSprite();

     if( SpriteFactory::instance()->getSpriteCount() == 0 )
     {
         SpriteFactory::free();
     }
}

/*
void Sprite2D::setTexture( render::Texture2D* texture )
{
    m_effect = SpriteFactory::instance()->getNewEffect( (render::Texture*)texture );

    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vCropStart", m_crop.getStart());
    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vCropSize", m_crop.getSize());
    updateTransform();
}*/

void Sprite2D::setCrop(const Rect& crop)
{
    math::vec2 vCropStart = crop.getStart();
    math::vec2 vCropSize = crop.getSize();

    assert( (vCropStart.x() >= 0.0) && (vCropStart.x() <= 1.0f) );
    assert( (vCropStart.y() >= 0.0) && (vCropStart.y() <= 1.0f) );

    assert( (vCropSize.x() >= 0.0) && (vCropSize.x() <= 1.0f) );
    assert( (vCropSize.y() >= 0.0) && (vCropSize.y() <= 1.0f) );

    assert( ((vCropSize.x()+vCropStart.x()) >= 0.0) && ((vCropSize.x()+vCropStart.x()) <= 1.01f) );
    assert( ((vCropSize.y()+vCropStart.y()) >= 0.0) && ((vCropSize.y()+vCropStart.y()) <= 1.01f) );

    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vecCropStart", vCropStart );
    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vecCropSize", vCropSize );

    m_crop = crop;
}

void Sprite2D::setCrop(const math::vec2& start, const math::vec2& size)
{
    setCrop( Rect(start,size) );
}

void Sprite2D::setPixelCrop(const Rect& crop)
{
    setPixelCrop( crop.getStart(), crop.getSize() );
}

void Sprite2D::setPixelCrop(const math::vec2& start, const math::vec2& size)
{
    math::vec2 scale = math::vec2( 1.0f/m_textureSize.x(), 1.0f/m_textureSize.y() );
    setCrop( start*scale, size*scale );
}

const graphics::Mesh* const Sprite2D::getMesh() const
{
    return SpriteFactory::instance()->getMesh();
}


const graphics::Effect* const Sprite2D::getEffect() const
{
    return m_effect.ptr();
}

const math::vec2& Sprite2D::getCropStart() const
{
    return m_crop.getStart();
}

const math::vec2& Sprite2D::getCropSize() const
{
    return m_crop.getSize();
}

math::vec2 Sprite2D::getPixelCropStart() const
{
    return m_crop.getStart() * math::vec2( m_textureSize.x(), m_textureSize.y() );
}

math::vec2 Sprite2D::getPixelCropSize() const
{
    return m_crop.getSize() * math::vec2( m_textureSize.x(), m_textureSize.y() );
}

void Sprite2D::updateTransform()
{
 //   math::mat4 rot;
 //   rot.rotate( m_rotation, 0,0,1);
 //   math::vec3 rotScale = rot * math::vec3(m_scale.x(), m_scale.y(), 1.0 );
    math::mat4 transform;
    transform.setToIdentity();

    transform.translate( m_translation.x(), m_translation.y() );
    transform.rotate( m_rotation, 0, 0, 1 );
    transform.scale( getPixelCropSize().x()*m_scale.x(), getPixelCropSize().y()*m_scale.y() );
    transform.translate( -m_pivot.x(), m_pivot.y() );

  //  m_effect->getPass(0)->getShaderUniforms()->setUniformValue("vScale", m_scale);
    m_effect->getPass(0)->getShaderUniforms()->setUniformValue("matTransform", transform);
}


}
}

